import { useCallback, useLayoutEffect, useRef } from 'react';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { tap, filter, catchError, concatMap, delay } from 'rxjs/operators';
import { of, race, timer } from 'rxjs';

export const fireEvent = function (eventName: string, data: any, target: EventTarget = document.body) {
  const event = new CustomEvent(eventName, {
    detail: data,
    bubbles: true,
    cancelable: false,
  });

  target.dispatchEvent(event);
};

class Socket {
  observables: {};

  subject: WebSocketSubject<any>;

  heartbeatSubscription: any;
  connectedCount: number;

  staleSubscription: any;

  idle: boolean;
  idleTimeout: any;

  opened: boolean;
  connectInProgress: boolean;

  constructor() {
    this.observables = {};
    this.connectedCount = 0;
    this.idle = true;
    this.opened = false;
    this.connectInProgress = true;

    const url = `ws://universe/rendering`;

    this.subject = webSocket({
      url,
      // serializer: (message: any) => {
      //   if (typeof message === 'string') {
      //     return message;
      //   }
      //   const { type, windowIndex, imageId, options } = message;
      //   const seq = ++this.total;
      //   if (imageId || options.updateRender) {
      //     this.requestedImageIds[seq] = {
      //       type,
      //       windowIndex,
      //       imageId,
      //       args: options.args,
      //       timeStamp: Date.now(),
      //     };
      //   }
      //   return Request.encode({
      //     Seq: seq,
      //     Biz: options.biz,
      //     DataPath: options.dataPath,
      //     UpdateRender: options.updateRender || false,
      //     Parameters: JSON.stringify({
      //       caseNum: options.caseId,
      //       action: options.action,
      //       args: options.args,
      //       extras: options.extras || {},
      //     }),
      //     replaceable: options.replaceable !== false,
      //   })
      //     .finish()
      //     .slice().buffer;
      // },
      // deserializer: (message) => {
      //   console.log(22222, message);

      //   return message;
      // },
      binaryType: 'arraybuffer',
      openObserver: {
        next: () => {
          console.log(`websocket ${url} opened`);
          this.opened = true;
          this.connectInProgress = false;
          fireEvent('websocketopened', true);
          if (!this.heartbeatSubscription) {
            this.startHeartBeat();
          }
        },
      },
      closeObserver: {
        next: () => {
          console.log(`websocket ${url} closed`);
          this.opened = false;
          this.connectInProgress = false;
          fireEvent('websocketopened', false);
        },
      },
    });
    this.staleSubscription = this.subject.subscribe();
  }

  startHeartBeat() {
    // ping-pong 心跳机制
    this.heartbeatSubscription = timer(10 * 1000, 30 * 1000)
      .pipe(
        tap(() => this.subject.next('ping')),
        concatMap((_) =>
          race(
            of('timeout').pipe(delay(5 * 1000)),
            this.subject.pipe(
              filter((msg) => msg === 'pong'),
              catchError(() => of('error'))
            )
          )
        )
      )
      .subscribe((msg) => {
        if (msg !== 'pong') {
          // this.close();
          console.error('failed to connect websocket with ping-pong');
        }
      });
  }

  getObservable(key) {
    if (!this.observables[key]) {
      this.observables[key] = this.subject.multiplex(
        () => 'ping',
        () => 'ping',
        (message) => `${message.type}${isNaN(message.windowIndex) ? '' : `_${message.windowIndex}`}` === key
      );
    }
    return this.observables[key];
  }

  connect(key, fn) {
    // if (this.closeTimeout) {
    //   clearTimeout(this.closeTimeout);
    //   this.closeTimeout = null;
    // }
    if (key && typeof fn === 'function') {
      const observable = this.getObservable(key);
      this.connectedCount++;
      return observable.subscribe(fn);
    }
  }

  disconnect(subscription) {
    if (subscription) {
      this.connectedCount--;
      subscription.unsubscribe();
    }
    if (this.connectedCount === 0) {
      this.close();
    }
  }

  sendMessage(message) {
    if (this.idleTimeout) {
      clearTimeout(this.idleTimeout);
      this.idleTimeout = null;
    }
    if (!this.idle) {
      this.idle = false;
      fireEvent('WEB_SOCKET_IDLE', false);
    }
    this.idleTimeout = setTimeout(() => {
      this.idle = true;
      fireEvent('WEB_SOCKET_IDLE', true);
    }, 2000);
    this.subject.next(message);
  }

  close() {
    if (this.subject) {
      if (this.heartbeatSubscription) {
        this.heartbeatSubscription.unsubscribe();
        this.heartbeatSubscription = null;
      }
      // this.closeTimeout = setTimeout(() => {
      console.log('close websocket connection');
      this.staleSubscription.unsubscribe();
      this.staleSubscription = null;
      this.subject.complete();
      // }, 60000);
    }
  }
}

let socket;
const initSocket = () => {
  if (!socket?.opened && !socket?.connectInProgress) {
    socket = new Socket();
    // @ts-ignore
    window.performanceTime = {};
  }
};

let minBiz = -1;
const usedBizs = new Set();

const useSocket = (defaultOptions) => {
  const defaultOptionsRef = useRef<any>();

  useLayoutEffect(() => {
    if (defaultOptionsRef.current) {
      return;
    }
    const windowIndex = defaultOptions?.biz ?? minBiz--;
    let biz = windowIndex;
    // 不重复使用 biz
    while (usedBizs.has(biz)) {
      if (biz >= 0) {
        biz++;
      } else {
        biz--;
      }
    }
    usedBizs.add(biz);
    defaultOptionsRef.current = {
      ...defaultOptions,
      type: defaultOptions?.type,
      biz,
    };
  }, [defaultOptions]);

  const onMessage = useCallback((message) => {
    console.log(1111, message);
  }, []);

  useLayoutEffect(() => {
    initSocket();
  }, [onMessage]);

  const sendMessage = useCallback(({ options }) => {
    const defaultOptions = defaultOptionsRef.current;
    const { type, biz } = defaultOptions;
    if (socket) {
      socket.sendMessage({
        type,
        options: {
          ...defaultOptions,
          ...options,
          biz: options.biz ?? biz,
          extras: {
            type,
            ...(options?.extras ?? {}),
          },
        },
      });
    }
  }, []);

  const onClose = useCallback(() => {
    const defaultOptions = defaultOptionsRef.current;
    return () => {
      const { biz } = defaultOptions;
      if (biz >= 0) {
        sendMessage({
          options: {
            action: 'CLOSE',
            args: {
              biz: biz,
            },
          },
        });
      }
    };
  }, [sendMessage]);

  // useEffect(() => {
  //   document.addEventListener('IMAGE_LAYOUT_UNMOUNT', onClose);
  //   return () => {
  //     document.removeEventListener('IMAGE_LAYOUT_UNMOUNT', onClose);
  //   };
  // }, [onClose]);

  return { sendMessage, onClose };
};

export default useSocket;
